import java.io.*;
import java.lang.reflect.Array;
import java.util.*;

public class CBC {

	///////PART A/////////
	private HashMap keys;
	private String IV;//The initial vector
	private String d;//The document
	private String key;//The key
	private List<String> blocks;//A list of blocks that are created from the document (the text)
	private List<String> cipher;//A list of ciphered blocks
	private int length;//The length of the IV, by this length the blocks are devided

	
	
	/*
	 * Initialization of the CBC class
	 */
	public CBC(){
	cipher = new LinkedList<String>();
	blocks = new LinkedList<String>();
	IV="";
	d="";
	key="";
	keys = new HashMap<Character, Character>();

	}
	
	
	/*
	 * The main method that runs the CBC algorithm, everything is ran from it.
	 * The method gets 3 paths: the IV, Key and document paths
	 * Reads it from the files into the class fields and runs the algorithm
	 */
	public void runCBC(String IV_path, String d_path, String key_path){
		//////////////READING FROM THE FILES INTO THE FIELDS/////////////
		try{
			IV=readFromFile(IV_path);
			}
			catch(Exception e){
				System.err.println("error");
			}
		try{
			d=readFromFile(d_path);
			}
			catch(Exception e){
				System.err.println("error");
			}
		try{
			key=readFromFile(key_path);
			}
			catch(Exception e){
				System.err.println("error");
			}
		order_keys(key);//erases the spaces from the key string
		makeBlocks();//divides the documents into blocks
		runCipher();//runs the cipher algorithm
		writeToFile(d_path);
	}
	


	/*
	 * The cipher algorithm
	 */
	private void runCipher(){
		String ans="";
		for (int i=0; i<blocks.size(); i++)
		{
			//for the first block ONLY:
			//the first xor is with the IV
			//only then, the encryption with the key
			if (i==0){
				ans=makeXor(IV, blocks.get(0));
				//ans=runKey(ans);
				cipher.add(ans);//adding to the cipher list
				ans="";
			}
			else{
				//from the second block and until the end
				//the xor is with the last ciphered block
				
				ans=makeXor(cipher.get(i-1), blocks.get(i));
				//ans=runKey(ans);
				cipher.add(ans);
				ans="";
				
			}
		}
		 
	}
	
	/**
	 * This method splits the document into blocks of size that is determined by the IV
	 * adds zero's in case of a shorter block
	 */
	private void makeBlocks(){
		blocks.clear();
        if (this.IV.endsWith("\n")) {
            this.IV = this.IV.substring(0, this.IV.length() - 2);
        }
        if (this.d.endsWith("\n")) {
            this.d = this.d.substring(0, this.d.length() - 2);
        }
		length=IV.length();//the last 2 characters are \n so we remove it and set the length for the block division
		String tempD=d;//The original document
		String tempBlock="";//a temp string that we will add to the list
		while (tempD.length()>=length){
			tempBlock=tempD.substring(0,length);
			tempD=tempD.substring(length);
			blocks.add(tempBlock);
			tempBlock="";
		}
		//if the remaining text is less than the length
		//we will pad it with 0's
		if (tempD.length()>0 && tempD.length()<length)
		{
			for (int i=tempD.length()-1; i<=length; i++){
				tempD=tempD+'\0';
			}
			blocks.add(tempD);
			tempBlock="";
		}
		if (blocks.get(blocks.size()-1)==""){
			blocks.remove(blocks.size()-1);
		}
	}
	
	/*
	 * This method deletes all spaces in the key document
	 */
	private void order_keys(String s)
	{
		String ans="";
		for (int i=0; i<s.length(); i++){
			if ((s.charAt(i)>='a' && s.charAt(i)<='z')||(s.charAt(i)>='A' && s.charAt(i)<='Z')){
				ans=ans+s.charAt(i);
			}
		}
		key=ans;
		for (int i=0; i<key.length(); i++){
			if(i%2==0)
				keys.put(key.charAt(i), key.charAt(i+1));
			
		}
	}
	/*
	 * This method receives 2 strings, calls the "xor" methods for each char
	 * It returns a new string made of symbols that the xor methods returned
	 */
	private String makeXor(String a, String b){
		String ans="";
		Character tempChar;
		for (int i=0; i<length; i++){
			tempChar=xor(a.charAt(i),b.charAt(i));
			if (tempChar>='a'&&tempChar<='z'||tempChar>='A'&&tempChar<='Z'){
                if (keys.containsKey(tempChar)) {
                    tempChar = (Character) keys.get(tempChar);
                }
			}
			ans=ans+(char)tempChar;
			//ans=ans+xor(a.charAt(i),b.charAt(i));
		}
		return ans;
	}


	
	/*
	 * READ FROM FILE
	 */
	public String readFromFile( String file ) throws IOException {
	    BufferedReader reader = new BufferedReader( new FileReader (file));
	    String         line = null;
	    StringBuilder  stringBuilder = new StringBuilder();
	    String         ls = System.getProperty("line.separator");

	    while( ( line = reader.readLine() ) != null ) {
	        stringBuilder.append( line );
	        stringBuilder.append( ls );
	    }

	    return stringBuilder.toString();
	}
	
	/*
	 * The xor function, receives 2 chars, casts them to ASCII
	 * runs xor and then casts back to a char
	 */
	public char xor (char a, char b)
	{
		char ans;
		int tempAns=(int)a^(int)b;
		ans=(char)tempAns;
		return ans;
	}
	
/*
Creates a file with the name "originalName_xip
 */
	private void writeToFile(String s){
		s=s.substring(0,s.length()-4);
		s=s+"_encrypted.txt";
		File file = new File(s);
		FileWriter fw;
		BufferedWriter bw = null;
		try {
			fw = new FileWriter(file.getAbsoluteFile());
			bw = new BufferedWriter(fw);
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		for (int i=0; i<cipher.size(); i++){
			try {
				bw.write(cipher.get(i));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		try {
			bw.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}



}
